/*
 * Copyright (c) 2012, Eric Coolman, Codename One and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Codename One designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *  
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 * 
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 * 
 * Please contact Codename One through http://www.codenameone.com/ if you 
 * need additional information or have any questions.
 */
package java.net;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.StringTokenizer;

/**
 * An implementation of a Univeral Resource Identifier (URI). While the output
 * is mostly compatible with the Java 6 API, there are a few somewhat subtle
 * differences:
 *
 * <pre>
 * 1) For socket related URIs, the toString() methods use semicolon (;) as the
 *    query marker instead of the normal question mark (?), and the parameters are separated
 *    with a semicolon instead of the normal ampersand (&amp;).  With this, the URIs are compatible
 *    with those used by J2ME socket connectors.  (The Java 6 API treats socket URIs as URNs).
 * 2) This implementation does not yet "rigorously parse IPv4" addresses like the Java 6 version does,
 *    the host address is simply stored as provided by the caller.  This will be enhanced using the
 *    InetAddress class when available.
 * 3) The characters defined as legal "other characters" are not all interpreted correctly, which
 *    just means some unicode characters will get encoded that aren't required to be.  The
 *    method URIHelper.isLegalUnicode() needs to be inspected further.
 * 4) Because of 3) toASCIIString() and toString() return the same value.
 * TODO: finish this list
 * </pre>
 *
 * @author Eric Coolman
 *
 * @see <a href="http://docs.oracle.com/javase/6/docs/api/java/net/URI.html">http://docs.oracle.com/javase/6/docs/api/java/net/URI.html</a>
 * @see <a href="http://en.wikipedia.org/wiki/Uniform_resource_identifier">http://en.wikipedia.org/wiki/Uniform_resource_identifier</a>
 * @see <a href="http://en.wikipedia.org/wiki/Uniform_resource_name">http://en.wikipedia.org/wiki/Uniform_resource_name</a>
 * @see <a href="http://www.ietf.org/rfc/rfc2396.txt">http://www.ietf.org/rfc/rfc2396.txt</a>
 * @see <a href="http://www.ietf.org/rfc/rfc2732.txt">http://www.ietf.org/rfc/rfc2732.txt</a>
 * @see <a href="http://tools.ietf.org/html/rfc2141">http://tools.ietf.org/html/rfc2141</a>
 */
public class URI {

    /**
     * Characters that are valid within a URI
     */
    static final String UNRESERVED_EXTRAS = "_-!.~'()*";
    /**
     * Characters that have special meaning within a URI.
     */
    static final String PUNCTUATION = ",;:$&+=";
    /**
     * More characters that have special meaning within a URI.
     */
    static final String RESERVED = PUNCTUATION + "?/[]@";
    /**
     * Character that separates the scheme from the rest of the URI.
     */
    static final char SCHEME_SEPARATOR = ':';
    /**
     * Character that separates the port value within an authority.
     */
    static final char PORT_SEPARATOR = ':';
    /**
     * Character that separates the user info value within an authority.
     */
    static final char USERINFO_SEPARATOR = '@';
    /**
     * Character that separates the path value from the authority
     */
    static final char PATH_SEPARATOR = '/';
    /**
     * A marker that identifies an authority value follows
     */
    static final String AUTHORITY_MARKER = "//";
    /**
     * Character that identifies an escaped octet value.
     */
    static final char QUOTE_MARKER = '%';
    /**
     * Character that identifies the start of a query.
     */
    static final char QUERY_MARKER = '?';
    /**
     * Character that separates arguments within a query
     */
    static final char QUERY_SEPARATOR = '&';
    /**
     * Character that separates name/value within a query parameter.
     */
    static final char PARAMETER_SEPARATOR = '=';
    /**
     * Character that separates arguments within a query for socket-based URLs.
     */
    static final char SOCKET_QUERY_SEPARATOR = ';';
    /**
     * Character that identifies the start of a fragment.
     */
    static final char FRAGMENT_SEPARATOR = '#';
    /**
     * The character that should be used for separating query for this URI (; or
     * &amp;).
     */
    private char querySeparator;
    /**
     * Flag that identifies if this URI has a scheme and starts with a '/'.
     */
    private boolean opaque;
    /**
     * Flag that identifies if this URI is relative.
     */
    private boolean absolute;
    /**
     * The encoded URI string value, without the scheme and fragment.
     */
    private String schemeSpecificPart;
    /**
     * The userinfo, host, and port segment of the URI.
     */
    private String authority;
    /**
     * The encoded userinfo part of the URI.
     */
    private String userInfo;
    /**
     * The encoded host value of the URI.
     */
    private String host;
    /**
     * The URI scheme, or null for opaque URIs.
     */
    private String scheme;
    /**
     * The encoded path value of the URI.
     */
    private String path;
    /**
     * The encoded query value of the URI.
     */
    private String query;
    /**
     * The encoded fragment value of the URI.
     */
    private String fragment;
    /**
     * The port value of the URI, or -1 if no port specified.
     */
    private int port;

    /**
     * Constructor to create a new URI object. The userInfo, path, query and
     * fragment should be unencoded values - they will be encoded as required.
     *
     * @param scheme the scheme of the URI (for URLs, this would be the
     * protocol), or null for relative URIs.
     * @param userInfo the unencoded userinfo segment (ie. username:password) or
     * null.
     * @param host the hostname or address, or null.
     * @param port the host port, or -1.
     * @param path the unencoded path segment.
     * @param query the unencoded query segment.
     * @param fragment the unencoded fragment (often referred to as the
     * 'reference' or 'anchor'), or null.
     * @throws URISyntaxException if any of the fragments are invalid.
     */
    public URI(String scheme, String userInfo, String host, int port, String path, String query, String fragment)
            throws URISyntaxException {
        init();
        setScheme(scheme);
        setAuthority(host, port, userInfo, true);
        setPath(path, true);
        setQuery(query, true);
        setFragment(fragment, true);
    }

    /**
     * Constructor to create a new URI object. The authority, path, query and
     * fragment should be unencoded values - they will be encoded as required.
     *
     * @param scheme the scheme of the URI (for URLs, this would be the
     * protocol), or null for relative URIs.
     * @param authority the unencoded authority segment (ie.
     * username:password@host:port, or simply: host) or null.
     * @param path the unencoded path segment.
     * @param query the unencoded query segment.
     * @param fragment the unencoded fragment (often referred to as the
     * 'reference' or 'anchor'), or null.
     * @throws URISyntaxException if any of the fragments are invalid.
     */
    public URI(String scheme, String authority, String path, String query, String fragment) throws URISyntaxException {
        init();
        setScheme(scheme);
        setAuthority(authority, true);
        setPath(path, true);
        setQuery(query, true);
        setFragment(fragment, true);
    }

    /**
     * Constructor for building URNs. The ssp and fragment should be unencoded
     * values - they will be encoded as required.
     *
     * Examples: mailto:user@codenameone.com sms:+5555551212 tel:+5555551212
     * isbn:9781935182962
     *
     * @param scheme
     * @param ssp the unencoded scheme specific part (everything except the
     * scheme and fragment)
     * @param fragment the unencoded fragment, or null
     * @throws URISyntaxException if any of the segments are invalid.
     */
    public URI(String scheme, String ssp, String fragment) throws URISyntaxException {
        init();
        setScheme(scheme);
        setSchemeSpecificPart(ssp, true);
        setFragment(fragment, true);
    }

    /**
     * Constructor that parses its values from a URI string. This method expects
     * all segments to be property encoded by the caller. The URIHelper class
     * can be used to encode segments.
     *
     * @param uriString a full encoded URI in string form to be parsed.
     * @throws URISyntaxException if any of the parsed segments are invalid.
     */
    public URI(String uriString) throws URISyntaxException {
        if (uriString == null) {
            throw new URISyntaxException(uriString, "Input is null");
        }
        init();
        parseURI(uriString);
    }

    /**
     * Internal - Set the default values.
     */
    void init() {
        absolute = true;
        querySeparator = QUERY_SEPARATOR;
        opaque = false;
        port = -1;
    }

    /**
     * Utility method - set the scheme, ensuring valid format, and determining
     * the query separator to use.
     *
     * @see <a href="http://en.wikipedia.org/wiki/Uniform_resource_name">http://en.wikipedia.org/wiki/Uniform_resource_name</a>
     */
    protected void setScheme(String scheme) throws URISyntaxException {
        if ((this.scheme = scheme) == null) {
            absolute = false;
        } else {
            if (isValidScheme(scheme) == false) {
                throw new URISyntaxException(scheme, "Invalid scheme");
            }
            if (isSocketScheme(scheme) == true) {
                querySeparator = SOCKET_QUERY_SEPARATOR;
            }
        }
    }

    /**
     * Utility method - set the scheme specific part, ensuring valid format. If
     * encode=true, then some elements will be run through the encoder (path,
     * userinfo, query, fragment), otherwise the elements will be validated for
     * proper encoding.
     */
    protected void setSchemeSpecificPart(String ssp, boolean encode) throws URISyntaxException {
        parseSchemeSpecificPart(ssp, true);
    }

    /**
     * Utility method - set the part, ensuring valid format. If encode=true,
     * then some elements will be run through the encoder (path, userinfo,
     * query, fragment), otherwise the elements will be validated for proper
     * encoding.
     */
    protected void setAuthority(String newAuthority, boolean encode) throws URISyntaxException {
        if ((this.authority = newAuthority) == null) {
            return;
        }
        if (authority.startsWith(AUTHORITY_MARKER)) {
            authority = authority.substring(AUTHORITY_MARKER.length());
            newAuthority = authority;
        }
        int index = newAuthority.indexOf(USERINFO_SEPARATOR);
        if (index != -1) {
            this.userInfo = newAuthority.substring(0, index);
            if (encode) {
                this.userInfo = URIHelper.encodeString(this.userInfo);
            } else {
                // validate
                URIHelper.decodeString(this.userInfo);
            }
            newAuthority = newAuthority.substring(index + 1);
        }
        index = newAuthority.indexOf(PORT_SEPARATOR);
        if (index == 0) {
            this.host = null;
            this.port = parseIntOption("port", newAuthority.substring(1));
        } else if (index == -1) {
            this.host = newAuthority;
            this.port = -1;
        } else {
            this.host = newAuthority.substring(0, index);
            this.port = parseIntOption("port", newAuthority.substring(index + 1));
        }
        if (encode == true) {
            // to reconstruct the authority with the encoded value
            setAuthority(host, port, userInfo, false);
        }
    }

    /**
     * Utility method to set the query. If parameter encode=true, then the
     * result will be encoded, otherwise the result will be validated to ensure
     * encoding is valid. Typically the multi-parameter constructors will call
     * this method with encode=true, and the single parameter construct will
     * pass encode=false.
     *
     * @param query
     * @param encode
     * @throws URISyntaxException
     */
    protected void setQuery(String query, boolean encode) throws URISyntaxException {
        if ((this.query = query) != null) {
            if (encode) {
                this.query = URIHelper.encodeString(this.query);
            } else {
                // validate
                URIHelper.decodeString(this.query);
            }
        }
    }

    /**
     * Utility method to set the path. If parameter encode=true, then the result
     * will be encoded, otherwise the result will be validated to ensure
     * encoding is valid. Typically the multi-parameter constructors will call
     * this method with encode=true, and the single parameter construct will
     * pass encode=false.
     *
     * @param path
     * @param encode
     * @throws URISyntaxException
     */
    protected void setPath(String path, boolean encode) throws URISyntaxException {
        if ((this.path = path) != null) {
            if (encode) {
                this.path = URIHelper.encodeString(this.path);
            } else {
                // validate
                URIHelper.decodeString(this.path);
            }
        }
    }

    /**
     * Utility method to construct the authority segment from given host, port,
     * and userinfo segments. If parameter encode=true, then the userinfo
     * segment will be encoded, otherwise the it will be validated to ensure
     * encoding is valid. Typically the multi-parameter constructors will call
     * this method with encode=true, and the single parameter construct will
     * pass encode=false.
     *
     * @param host
     * @param port
     * @param userInfo
     * @param encode
     * @throws URISyntaxException
     */
    protected void setAuthority(String host, int port, String userInfo, boolean encode) throws URISyntaxException {
        StringBuffer buffer = new StringBuffer();
        if ((this.userInfo = userInfo) != null) {
            if (encode) {
                this.userInfo = URIHelper.encodeString(this.userInfo);
            } else {
                // validate
                URIHelper.decodeString(this.userInfo);
            }
            buffer.append(this.userInfo + USERINFO_SEPARATOR);
        }
        if ((this.host = host) != null) {
            buffer.append(host);
        }
        if ((this.port = port) != -1) {
            buffer.append(PORT_SEPARATOR);
            buffer.append(port);
        }
        authority = buffer.toString();
    }

    /**
     * Utility method to set the fragment. If parameter encode=true, then the
     * result will be encoded, otherwise the result will be validated to ensure
     * encoding is valid. Typically the multi-parameter constructors will call
     * this method with encode=true, and the single parameter construct will
     * pass encode=false.
     *
     * @param fragment
     * @param encode
     */
    protected void setFragment(String fragment, boolean encode) {
        if ((this.fragment = fragment) != null && encode) {
            this.fragment = URIHelper.encodeString(this.fragment);
        }
    }

    /**
     * Utility method to construct the scheme specific part from the uri
     * segments (less scheme and fragment)
     *
     * @return
     */
    protected String rebuildSchemeSpecificPart() {
        StringBuffer buffer = new StringBuffer();
        if (opaque == false && (host != null || port != -1)) {
            buffer.append(AUTHORITY_MARKER);
            if (userInfo != null) {
                buffer.append(getRawUserInfo() + USERINFO_SEPARATOR);
            }
            if (host != null) {
                buffer.append(getHost());
            }
            if (port != -1) {
                buffer.append(PORT_SEPARATOR);
                buffer.append(getPort());
            }
        }
        if (path != null) {
            buffer.append(getRawPath());
        }
        if (query != null) {
            if (querySeparator == SOCKET_QUERY_SEPARATOR) {
                buffer.append(querySeparator + getRawQuery().replace(QUERY_SEPARATOR, SOCKET_QUERY_SEPARATOR));
            } else {
                buffer.append(QUERY_MARKER + getRawQuery());
            }
        }
        return buffer.toString();
    }

    /**
     * A convenience factory method, intended to be used when the URI string is
     * known to be valid (ie. a static application URI), so it is not needed for
     * the caller to handle invalid syntax. NOTE: this is not away to avoid
     * handling errors altogether - passing an invalid URI string will result in
     * an IllegalArgumentException being thrown. The benefit here is that the
     * compiler will not complain if you don't explicitly handle the error at
     * compile time.
     *
     * When handling a user-editable URI, use the URI constructors instead.
     *
     * @param uriString URI address as a string
     * @return parsed URI object
     */
    public static URI create(String uriString) {
        URI uri;
        try {
            uri = new URI(uriString);
        } catch (URISyntaxException e) {
            throw new IllegalArgumentException(e.toString());
        }
        return uri;
    }

    /**
     * Rather than attempting to process the uri string in a linear fashion,
     * this implementation works its way from outside-in
     *
     * @param uriString
     * @throws URISyntaxException
     */
    protected void parseURI(String uriString) throws URISyntaxException {
        String s = uriString;
        int index = s.indexOf(FRAGMENT_SEPARATOR);
        if (index != -1) {
            setFragment(s.substring(index + 1), false);
            s = s.substring(0, index);
        }
        index = s.indexOf(SCHEME_SEPARATOR);
        if (index != -1) {
            String scheme = s.substring(0, index);
            if (isValidScheme(scheme)) {
                setScheme(scheme);
                s = s.substring(index + 1);
            }
        }
        parseSchemeSpecificPart(s, false);
    }

    /**
     * Utility method used to parse a given scheme specific part. If parameter
     * encode=true, then the result will be encoded, otherwise the result will
     * be validated to ensure encoding is valid. Typically the multi-parameter
     * constructors will call this method with encode=true, and the single
     * parameter construct will pass encode=false.
     *
     * @param ssp scheme specific part (the URI without the scheme or fragment
     * included).
     * @param encode true if ssp needs to be encoded, false if ssp needs to be
     * verified.
     * @throws URISyntaxException if the ssp is invalid.
     */
    protected void parseSchemeSpecificPart(String ssp, boolean encode) throws URISyntaxException {
        if (ssp == null) {
            throw new URISyntaxException(ssp, "Invalid scheme specific part");
        }
        if (scheme != null && ssp.charAt(0) != PATH_SEPARATOR) {
            this.opaque = true;
            this.schemeSpecificPart = ssp;
            return;
        }
        int index;
        String s = ssp;

        if ((index = s.lastIndexOf(QUERY_MARKER)) != -1) {
            setQuery(s.substring(index + 1), encode);
            s = s.substring(0, index);
        } else if (getQuerySeparator() == SOCKET_QUERY_SEPARATOR && (index = s.indexOf(SOCKET_QUERY_SEPARATOR)) != -1) {
            setQuery(s.substring(index + 1).replace(SOCKET_QUERY_SEPARATOR, QUERY_SEPARATOR), encode);
            s = s.substring(0, index);
        }

        index = (s.startsWith(AUTHORITY_MARKER)) ? 2 : 0;
        index = s.indexOf(PATH_SEPARATOR, index);
        if (index != -1) {
            setPath(s.substring(index), encode);
            s = s.substring(0, index);
        }
        setAuthority(s, encode);
    }

    /**
     * Internal utility method to throw a syntax error if a value can not be
     * parsed.
     *
     * @param key name of the value being parsed, for error reporting.
     * @param value value to be parsed.
     * @return parsed integer.
     * @throws URISyntaxException if value can not be parsed.
     */
    int parseIntOption(String key, String value) throws URISyntaxException {
        try {
            return Integer.parseInt(value);
        } catch (NumberFormatException nfe) {
            throw new URISyntaxException(value, "Invalid " + key + " value: " + value);
        }
    }

    /**
     * Internal utility method to determine if the given scheme should use
     * semicolons (;) for query separator instead of ampersand (&amp;)
     */
    boolean isSocketScheme(String scheme) {
        for (String p : URIHelper.SOCKET_SCHEMES) {
            if (p.equals(scheme)) {
                return true;
            }
        }
        return false;
    }

    /**
     * Verifies the scheme contains only valid characters as per the URN
     * specification (see NID).
     *
     * @see <a href="http://tools.ietf.org/html/rfc2141">http://tools.ietf.org/html/rfc2141</a>
     */
    boolean isValidScheme(String scheme) {
        for (int i = 0; i < scheme.length(); i++) {
            char ch = scheme.charAt(i);
            if (URIHelper.isAlpha(ch) || URIHelper.isNumeric(ch) || ch == '-') {
                continue;
            }
            return false;
        }
        return true;
    }

    /**
     * Get the scheme part of the URI.
     *
     * @return the scheme part of the URI.
     */
    public String getScheme() {
        return scheme;
    }

    /**
     * Get the host name part of the URI.
     *
     * @return the host name part of the URI.
     */
    public String getHost() {
        return host;
    }

    /**
     * Get the port number for this URI.
     *
     * @return the port number for this URI, or -1 if a port number was not
     * specified.
     */
    public int getPort() {
        return port;
    }

    /**
     * Get the decoded path part of the uri.
     *
     * @return the query part of the URI, or an empty string if no path is
     * included in the URI.
     */
    public String getPath() {
        return URIHelper.decodeString(path, true);
    }

    /**
     * Get the encoded path part of the uri.
     *
     * @return the query part of the URI, or an empty string if no path is
     * included in the URI.
     */
    public String getRawPath() {
        return path;
    }

    /**
     * Get the decoded query part of the uri. The query marker (?) itself is not
     * included in the result.
     *
     * @return the query part of the URI.
     */
    public String getQuery() {
        return URIHelper.decodeString(query, true);
    }

    /**
     * Get the encoded query part of the uri. The query marker (?) itself is not
     * included in the result.
     *
     * @return the query part of the URI.
     */
    public String getRawQuery() {
        return query;
    }

    /**
     * Get the decoded fragment (otherwise known as the &quot;reference&quot; or
     * &quot;anchor&quot;) part of the uri. The anchor marker (#) itself is not
     * included in the result.
     *
     * @return the anchor part of the URI.
     */
    public String getFragment() {
        return URIHelper.decodeString(fragment, true);
    }

    /**
     * Get the encoded fragment (otherwise known as the &quot;reference&quot; or
     * &quot;anchor&quot;) part of the uri. The anchor marker (#) itself is not
     * included in the result.
     *
     * @return the anchor part of the URI.
     */
    public String getRawFragment() {
        return fragment;
    }

    /**
     * @return the schemeSpecificPart
     */
    public String getSchemeSpecificPart() {
        return URIHelper.decodeString(getRawSchemeSpecificPart(), true);
    }

    /**
     * @return the schemeSpecificPart
     */
    public String getRawSchemeSpecificPart() {
        if (schemeSpecificPart == null) {
            schemeSpecificPart = rebuildSchemeSpecificPart();
        }
        return schemeSpecificPart;
    }

    /**
     * @return the authority
     */
    public String getAuthority() {
        return URIHelper.decodeString(authority, true);
    }

    /**
     * @return the authority
     */
    public String getRawAuthority() {
        return authority;
    }

    /**
     * @return the userInfo
     */
    public String getUserInfo() {
        return URIHelper.decodeString(userInfo, true);
    }

    /**
     * @return the userInfo
     */
    public String getRawUserInfo() {
        return userInfo;
    }

    /**
     * @return true if this URI has a scheme and starts with a slash
     */
    public boolean isOpaque() {
        return opaque;
    }

    /**
     * @return true if the URI is not a relative URI.
     */
    public boolean isAbsolute() {
        return absolute;
    }

    /**
     * Get the character used for separating query. Normally this will return
     * '?'. On J2ME Connector URLs, this method will return ';'.
     */
    char getQuerySeparator() {
        return querySeparator;
    }

    /**
     * @return the uri as a string
     */
    public String toString() {
        return toASCIIString();
    }

    /**
     * @return the uri as a string with parts encoded.
     */
    public String toASCIIString() {
        StringBuffer buffer = new StringBuffer();
        if (scheme != null) {
            buffer.append(scheme + SCHEME_SEPARATOR);
        }
        buffer.append(getRawSchemeSpecificPart());
        if (fragment != null) {
            buffer.append(FRAGMENT_SEPARATOR + getRawFragment());
        }
        return buffer.toString();
    }

    /**
     * Create a relative URI object against this URI, given the uri parameter.
     *
     * @param uri
     * @return
     * @see <a href="http://docs.oracle.com/javase/6/docs/api/java/net/URI.html#relativize%28java.net.URI%29">http://docs.oracle.com/javase/6/docs/api/java/net/URI.html#relativize%28java.net.URI%29</a>
     */
    public URI relativize(URI uri) {
        if (isOpaque() || uri.isOpaque()) {
            return uri;
        }
        if (getScheme() == null || uri.getScheme() == null || getScheme().equals(uri.getScheme()) == false) {
            return uri;
        }
        String thisAuthority = null;
        String thatAuthority = null;
        String thisPath = null;
        String thatPath = null;
        if ((thisAuthority = getAuthority()) == null || (thatAuthority = uri.getAuthority()) == null
                || thisAuthority.equals(thatAuthority) == false) {
            return uri;
        }
        if ((thisPath = getPath()) == null || (thatPath = uri.getPath()) == null
                || thatPath.startsWith(thisPath + PATH_SEPARATOR) == false) {
            return uri;
        }
        try {
            return new URI(null, null, thatPath.substring(thisPath.length() + 1), uri.getQuery(), uri.getFragment());
        } catch (URISyntaxException e) {
            // Since the two URIs are pre-validated, we should never get here.
            throw new IllegalArgumentException(e.getMessage());
        }
    }

    /**
     * Resolve a relative URI by merging it with this URI.
     *
     * @param uri a URI to resolve against this URI.
     * @return a new URI created by merging given URI with this URI.
     * @see <a href="http://docs.oracle.com/javase/6/docs/api/java/net/URI.html#resolve%28java.net.URI%29">http://docs.oracle.com/javase/6/docs/api/java/net/URI.html#resolve%28java.net.URI%29</a>
     */
    public URI resolve(URI uri) {
        if (isOpaque() || uri.isAbsolute()) {
            return uri;
        }
        String thatPath = uri.getPath();
        String thatQuery = uri.getQuery();
        String thatAuthority = uri.getAuthority();
        String thatFragment = uri.getFragment();
        try {
            // if standalone fragment was passed.
            if (thatFragment != null
                    && (uri.getScheme() == null && thatPath == null && thatQuery == null && thatAuthority == null)) {
                return new URI(getScheme(), getAuthority(), getPath(), getQuery(), thatFragment);
            }
            if (thatAuthority != null) {
                return new URI(getScheme(), thatAuthority, thatPath, thatQuery, thatFragment);
            }
            // an absolute path was passed
            if (thatPath != null && thatPath.charAt(0) == PATH_SEPARATOR) {
                return new URI(getScheme(), getAuthority(), thatPath, thatQuery, thatFragment);
            }
            // a relative path was passed
            String thisPath = getPath();
            if (thisPath != null) {
                int index = thisPath.lastIndexOf(PATH_SEPARATOR);
                if (index != -1) {
                    thisPath = thisPath.substring(index);
                }
            }
            thisPath += PATH_SEPARATOR + thatPath;
            return new URI(getScheme(), getAuthority(), thisPath, thatQuery, thatFragment).normalize();
        } catch (URISyntaxException use) {
            // since both uri's are already validated, should never get here
            throw new IllegalArgumentException(use.getMessage());
        }

    }

    /**
     * Normalize a URI by removing any "./" segments, and "path/../" segments.
     *
     * @return a new URI instance with redundant segments removed.
     * @see <a href="http://docs.oracle.com/javase/6/docs/api/java/net/URI.html#normalize%28%29">http://docs.oracle.com/javase/6/docs/api/java/net/URI.html#normalize%28%29</a>
     */
    public URI normalize() {
        String thisPath = getPath();
        StringTokenizer st = new StringTokenizer(thisPath, String.valueOf(PATH_SEPARATOR));
        List<String> segments = new ArrayList<String>();
        while (st.hasMoreTokens()) {
            segments.add(st.nextToken());
        }
        List<Integer> removals = new ArrayList<Integer>();
        for (int i = 0; i < segments.size(); i++) {
            String segment = segments.get(i);
            if (segment.equals(".")) {
                removals.add(0, i);
                continue;
            } else if (i > 0 && segment.equals("..")) {
                if (segments.get(i - 1).equals("..") == false) {
                    removals.add(0, i - 1);
                    removals.add(0, i);
                    continue;
                }
            }
        }
        Iterator<Integer> iter = removals.iterator();
        while (iter.hasNext()) {
            segments.remove(iter.next().intValue());
        }
        StringBuilder buffer = new StringBuilder();
        for (int i = 0; i < segments.size(); i++) {
            String segment = segments.get(i);
            if (i == 0) {
                if (isAbsolute()) {
                    buffer.append(PATH_SEPARATOR);
                } else if (segment.indexOf(SCHEME_SEPARATOR) != -1) {
                    buffer.append('.');
                    buffer.append(PATH_SEPARATOR);
                }
                buffer.append(segment);
                continue;
            }
            buffer.append(PATH_SEPARATOR);
            buffer.append(segment);
        }
        try {
            return new URI(getScheme(), getUserInfo(), getHost(), getPort(), buffer.toString(), getQuery(),
                    getFragment());
        } catch (URISyntaxException e) {
			// since both URIs should already be valid, we should never get
            // here.
            throw new IllegalArgumentException(e.getMessage());
        }
    }

    /* (non-Javadoc)
     * @see java.lang.Object#hashCode()
     */
    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + ((fragment == null) ? 0 : fragment.hashCode());
        result = prime * result + ((scheme == null) ? 0 : scheme.hashCode());
        result = prime * result + ((schemeSpecificPart == null) ? 0 : schemeSpecificPart.hashCode());
        return result;
    }

    /* (non-Javadoc)
     * @see java.lang.Object#equals(java.lang.Object)
     */
    @Override
    public boolean equals(Object that) {
        if (this == that) {
            return true;
        }
        if (that == null) {
            return false;
        }
        if (getClass() != that.getClass()) {
            return false;
        }
        URI other = (URI) that;
        // compare decoded fragment
        if (fragment == null) {
            if (other.fragment != null) {
                return false;
            }
        } else if (!getFragment().equals(other.getFragment())) {
            // the decoded value should be the same
            return false;
        }
        if (scheme == null) {
            if (other.scheme != null) {
                return false;
            }
        } else if (!scheme.equals(other.scheme)) {
            return false;
        }
        // compare decoded ssp
        if (schemeSpecificPart == null) {
            if (other.schemeSpecificPart != null) {
                return false;
            }
        } else if (!getSchemeSpecificPart().equals(other.getSchemeSpecificPart())) {
            return false;
        }
        return true;
    }
}
